/**
 * Copyright 2009 University of Washington This code is released under the GPL version 3 - with a copy of the license
 * included
 * 
 * @author Maya Hao Li
 */
package seedpod.webapp;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

import seedpod.exceptions.SeedpodException;
import seedpod.kb2db.ModelMap;
import seedpod.kb2db.SlotMap;
import seedpod.model.NoObjectFoundException;
import seedpod.model.SeedpodDO;
import seedpod.model.SeedpodModel;
import seedpod.model.SeedpodObjectFactory;
import seedpod.model.SeedpodModel.RdbCls.USER_GROUP;
import seedpod.model.custom.AccessLog;
import seedpod.model.custom.SeedpodUser;
import seedpod.model.rdb.Rdb;
import seedpod.rdb.PMFactory;
import seedpod.rdb.PManager;
import seedpod.rdb.RdbConnectParameters;
import seedpod.rdb.RdbConnection;
import seedpod.webapp.service.PasswordService;

import edu.stanford.smi.protege.model.KnowledgeBase;
import edu.stanford.smi.protege.model.Project;

/**
 * This class is called only once at the start of the server. This behavior is specified in web.xml. This class is
 * responsible for starting a rdb connection, persistence manager factory. It also makes sure that the application has
 * at least one admin user at the very first start up of the server.
 * 
 * @author maya
 */

public class Seedpod extends HttpServlet {

	private static final long serialVersionUID = -5867473437539525014L;
	private static ModelMap _modelMap;
	private static KnowledgeBase _kb;
	private static Rdb _rdb;
	private static RdbConnectParameters _dbParam;
	private static RdbConnection _rdbConnection;

	public static String getAbsoluteUrl( String path ) {
		String newPath = _SERVLET_ROOT_URL;
		if ( path.startsWith( "/" ) ) path = path.substring( 1, path.length() );
		newPath += path;
		return newPath;
	}

	public static KnowledgeBase getKnowledgeBase() {
		return _kb;
	}

	public static ModelMap getModelMap() {
		return _modelMap;
	}

	public static PMFactory getPersistenceManagerFactory() {
		return PMFactory.getInstance();
	}

	public static PManager getPManager() {
		return PMFactory.getInstance().createManager( _rdbConnection );
	}

	public static RdbConnectParameters getRdbConnectParameters() {
		return _dbParam;
	}

	// use default db connection
	public static SeedpodDO getSeedpodObjectInstance( int oid ) throws NoObjectFoundException, SeedpodException {
		PMFactory factory = getPersistenceManagerFactory();
		PManager manager = factory.createManager();
		return manager.createObject( oid );
	}

	public static String getWebappUrlRoot() {
		return _SERVLET_ROOT_URL;
	}

	private PMFactory _rdbFactory;

	private static String _SERVLET_ROOT_URL = LimsContext.getContextParameter( LimsContext.PARAM_WEBAPP_URL_PATH );

	@Override
	public void init( ServletConfig servletConfig ) throws ServletException {
		super.init( servletConfig );

		initKB();
		initRdbParam();
		initRdbConnection();
		initRdb();
		initModelMap();
		initRdbFactory();
		initObjectFactory();
		initAdmin();
	}

	/**
	 * code initializes the system admin user. only needs to be run once for the web app at start up.
	 */
	private void initAdmin() {
		PManager manager = null;
		try {
			manager = _rdbFactory.createManager();
		} catch (SeedpodException e2) {
			throw new RuntimeException( "Cannot connect to the database." );
		}

		SeedpodUser adminUser = null;
		String adminUserName = LimsContext.getContextParameter( LimsContext.PARAM_APPLICATION_ADMIN_NAME );
		String adminUserPwd = LimsContext.getContextParameter( LimsContext.PARAM_APPLICATION_ADMIN_PASSWORD );

		try {
			adminUser = new SeedpodUser( adminUserName, true, manager );

			// create the adminUser if it doesn't already exist.
			if ( !adminUser.exists() ) {
				SlotMap userName = adminUser.getAttribute( SeedpodModel.RdbSlot.USERNAME );
				SlotMap password = adminUser.getAttribute( SeedpodModel.RdbSlot.PASSWORD );
				SlotMap permission = adminUser.getAttribute( SeedpodModel.RdbSlot.USER_GROUP );

				adminUser.setAttributeValue( userName, adminUserName );
				adminUser.setAttributeValue( password, PasswordService.getInstance().encrypt( adminUserPwd ) );
				adminUser.setAttributeValue( permission, USER_GROUP.ADMINISTRATOR.toString() );

				adminUser.create();
			}

		} catch (Exception e) {
			throw new RuntimeException( "System exits after failing to create admin user in database which is required to proceed." );
		}
	}

	private void initKB() {
		String kbPath = LimsContext.getContextParameter( LimsContext.PARAM_KNOWLEDGE_BASE_PATH );
		URI kbURI;
		URL kburl = this.getClass().getResource( kbPath );
		Collection<String> errors = new ArrayList<String>();
		Project protege = null;

		try {
			kbURI = kburl.toURI();
			protege = Project.loadProjectFromURI( kbURI, errors );
			System.out.println( protege.getName() + " knowledge base opened successfully " );
		} catch (URISyntaxException e) {
			errors.add( e.toString() );
		}

		if ( errors.size() == 0 )
			_kb = protege.getKnowledgeBase();
		else {
			System.err.println( "Fail to start initialize seedpod knowledge base: " + kbPath );
			System.exit( -1 );
		}

	}

	private void initModelMap() {
		_modelMap = ModelMap.getInstance();
		_modelMap.setRdb( _rdb );
		_modelMap.loadMap( _rdbConnection );
	}

	// TODO Implement. This initialization needs to start from seedpod's xml config file
	private void initObjectFactory() {
		SeedpodObjectFactory objFactory = SeedpodObjectFactory.getInstance();
		objFactory.register( SeedpodModel.RdbCls.USER, SeedpodUser.class );
		objFactory.register( SeedpodModel.RdbCls.ACCESS_LOG, AccessLog.class );
	}

	private void initRdb() {
		String dbName = LimsContext.getContextParameter( LimsContext.PARAM_DATABASE );
		_rdb = new Rdb( dbName );
	}

	private void initRdbConnection() {
		_rdbConnection = RdbConnection.instance();
		_rdbConnection.setConnectionParameters( _dbParam );
	}

	private void initRdbFactory() {
		_rdbFactory = PMFactory.getInstance();
		_rdbFactory.addModelMap( _rdbConnection, _modelMap );
		_rdbFactory.addRdbConnection( _dbParam.getDatabaseName(), _rdbConnection );
		_rdbFactory.setDefaultRdbConnection( _rdbConnection );
	}

	private void initRdbParam() {
		String driver = LimsContext.getContextParameter( LimsContext.PARAM_DATABASE_DRIVER );
		String url = LimsContext.getContextParameter( LimsContext.PARAM_DATABASE_URL );
		String user = LimsContext.getContextParameter( LimsContext.PARAM_DATABASE_USER );
		String pw = LimsContext.getContextParameter( LimsContext.PARAM_DATABASE_PASSWORD );
		String database = LimsContext.getContextParameter( LimsContext.PARAM_DATABASE );

		_dbParam = new RdbConnectParameters( driver, url, user, pw, database );

	}

}
